#ifndef PREF_DIALOG_H_
#define PREF_DIALOG_H_

#include "p_general.h"
#include "p_network.h"
#include "p_command.h"
#include "p_record.h"
#include "p_visual.h"
#include "p_channellist.h"
#include "p_favorite.h"
#include "p_menu.h"
#include "p_input.h"
#include "p_notification.h"
#include "p_migemo.h"

namespace gpyp {
namespace pref {

class Dialog : public Gtk::Dialog {
private:
  class PageBase;
  class PageGroup : boost::noncopyable {
  private:
    typedef std::vector<PageBase *> PageList;
    typedef PageList::iterator iterator;

  private:
    Gtk::Notebook &notebook_;
    PageList       pages_;

    inline iterator find(PageBase &page) {
      return std::find(pages_.begin(), pages_.end(), &page);
    }

    inline bool notebook_on_switch_page_idle(PageBase *page) {
      init_page(*page);
      return false;
    }
    inline void notebook_on_switch_page(GtkNotebookPage */*page*/, guint page_num) {
      for(iterator iter = pages_.begin(); iter != pages_.end(); ++iter) {
        PageBase *p = *iter;
        if (p->page_num_ == static_cast<gint>(page_num)) {
          if (!p->initialized_) {
            Glib::signal_idle().connect(sigc::bind(sigc::mem_fun(*this, &PageGroup::notebook_on_switch_page_idle), p));
          }
          break;
        }
      }
    }

  public:
    inline PageBase *find(PrefPage id) {
      for(std::vector<PageBase *>::iterator iter = pages_.begin();
          iter != pages_.end(); ++iter) {
        if ((*iter)->get_id() == id) return *iter;
      }
      return NULL;
    }
    inline PageBase *find(const Glib::ustring &name) {
      for(std::vector<PageBase *>::iterator iter = pages_.begin();
          iter != pages_.end(); ++iter) {
        if ((*iter)->get_name() == name) return *iter;
      }
      return NULL;
    }

    inline void init_page(PageBase &page) {
      if (!page.initialized_) {
        page.initialized_ = true;
        page.label_->hide();
        page.box_->pack_start(*page.get_widget());
      }
    }
    inline void init_page(PrefPage id) {
      PageBase *p = find(id);
      if (p) init_page(*p);
    }

    inline void set_page(PageBase &page) {
      notebook_.set_current_page(page.page_num_);
    }
    inline void set_page(PrefPage id) {
      PageBase *p = find(id);
      if (p) set_page(*p);
    }
    inline void set_page(const Glib::ustring &name) {
      PageBase *p = find(name);
      if (p) set_page(*p);
    }

    inline void add(PageBase &page, const Glib::ustring &label) {
      iterator iter = find(page);
      if (iter == pages_.end()) {
        page.label_ = Gtk::manage(new Gtk::Label(_("Initializing...")));
        page.label_->show();
        page.label_->set_no_show_all(true);
        page.box_   = Gtk::manage(new Gtk::VBox());
        page.box_->pack_start(*page.label_);
        page.box_->set_border_width(2);
        page.page_num_ = notebook_.append_page(*page.box_, label);
        pages_.push_back(&page);
      }
    }
    inline void remove(PageBase &page) {
      iterator iter = find(page);
      if (iter != pages_.end()) pages_.erase(iter);
    }

    inline PageGroup(Gtk::Notebook &notebook) :
      notebook_(notebook)
    {
      notebook_.signal_switch_page()
        .connect(sigc::mem_fun(*this, &PageGroup::notebook_on_switch_page));
    }
  };
  class PageBase : boost::noncopyable {
  public:
    Gtk::VBox  *box_;
    Gtk::Label *label_;
    bool        initialized_;
    int         page_num_;

    virtual Gtk::Widget *get_widget() = 0;
    virtual PrefPage get_id() const = 0;
    virtual const Glib::ustring &get_name() const = 0;

    inline PageBase() :
      box_        (NULL),
      label_      (NULL),
      initialized_(false),
      page_num_   (-1)
    {
    }
    inline virtual ~PageBase() {
    }
  };
  template<typename T>
  class Page : public PageBase {
  private:
    PageGroup    &group_;
    Application  &app_;
    Gtk::Window  &parent_;
    T            *widget_;
    PrefPage      id_;
    Glib::ustring name_;

  public:
    inline Gtk::Widget *get_widget() {
      if (!widget_) {
        widget_ = Gtk::manage(new T(app_, parent_));
        widget_->show_all();
      }
      return widget_;
    }
    inline PrefPage get_id() const {
      return id_;
    }
    inline const Glib::ustring &get_name() const {
      return name_;
    }

    inline T *operator->() {
      return widget_;
    }

    inline Page(PageGroup           &group,
                Application         &app,
                Gtk::Window         &parent,
                PrefPage             id,
                const Glib::ustring &name,
                const Glib::ustring &label) :
      group_ (group),
      app_   (app),
      parent_(parent),
      widget_(NULL),
      id_    (id),
      name_  (name)
    {
      group_.add(*this, label);
    }
  };

private:
  Application             &app_;

  Gtk::Notebook            notebook_;
  PageGroup                pagegroup_;
  Page<General>            general_;
  Page<Network>            network_;
  Page<Command>            command_;
  Page<Record>             record_;
  Page<Visual>             visual_;
  Page<ChannelList>        channellist_;
  Page<FavoriteCollection> favorite_;
  Page<Menu>               menu_;
  Page<Input>              input_;
  Page<Notification>       notification_;
  Page<Migemo>             migemo_;

protected:
  virtual void on_response(int response_id);

public:
  inline void init_page(PrefPage page) {
    pagegroup_.init_page(page);
  }

  inline void show(PrefPage page = PREF_PAGE_NONE) {
    Gtk::Dialog::show();
    pagegroup_.set_page(page);
  }
  inline void show(const Glib::ustring &name) {
    Gtk::Dialog::show();
    pagegroup_.set_page(name);
  }

  Dialog(Application &app, Gtk::Window &parent);
};

class FavoriteDialog : public Gtk::Dialog {
private:
  Application       &app_;
  FavoriteCollection favorite_;

protected:
  virtual void on_response(int response_id);

public:
  inline void show() {
    Gtk::Dialog::show();
  }
  inline void show(RefFavorite favorite) {
    show();
    favorite_.select(favorite);
  }

  FavoriteDialog(Application &app, Gtk::Window &parent);
};

class PlayerDialog : public Gtk::Dialog {
private:
  PlayerBox player_;

public:
  PlayerDialog(Application &app, Gtk::Window &parent);
};

}/*pref*/
}/*gpyp*/

#endif/*PREF_DIALOG_H_*/
